<?php
/**
 * Smart Library - Smart Framework
 * http://smart-framework.highet.eu
 *
 * @category   SmartL
 * @package    SmartL_Config
 * @copyright  Copyright (c) 2005-2008 Smart Framework team - Stepan Sindelar, Vojtech Vit
 * @license    http://www.opensource.org/licenses/bsd-license.php	New BSD License
 */

/**
 * @see SmartL_Config_Storage
 */
require_once 'SmartL/Config/Storage.php';
/**
 * @see SmartL_Config_Storage_Array
 */
require_once 'SmartL/Config/Storage/Array.php';

/**
 * SmartL_Config is used to parse special xml configuration files. These
 * files offer advanced configuration definition features as arrays,
 * smart extending and simple browsing.
 * 
 * @package SmartL_Config
 */
class SmartL_Config extends SmartL_Config_Storage {
	/**
	 * Name of include tag.
	 *
	 */
	const INCLUDE_TAG = 'include';
	/**
	 * Name of file attribute of include tag.
	 *
	 */
	const INCLUDE_FILE_ATTRIBUTE = 'file';
	
	private $_defaultRewriteMode = SmartL_Config_Storage::REWRITE_MODE_NONE;
	private $_loaded = false;
	private $_dirname = null;
	/**
	 * List of files that extends this configuration and the file that was loaded first.
	 * For caching purposes.
	 *
	 * @var array
	 */
	private $_loadedFiles = array ();
	/**
	 * To prevent building of cacheId more than once for one $_loadedFiles array.
	 * The count is count of items in $_loadedFiles, for check if new id should be created.
	 *
	 * @var array
	 */
	private $_cacheId = array ('count' => 0, 'id' => '' );
	/**
	 * Cache for all configs.
	 *
	 * @var Zend_Cache_Core
	 */
	private static $_cache = null;
	
	/**
	 * @param Zend_Cache_Core $cache
	 */
	public static function setCache(Zend_Cache_Core $cache) {
		self::$_cache = $cache;
	}
	
	/**
	 * Loads given configuration file
	 *
	 * @param string $filename Configuration file name (path)
	 */
	public function load($filename) {
		if ($this->_loaded) {
			require_once 'SmartL/Config/Exception.php';
			throw new SmartL_Config_Exception ( "Configuration was already loaded. Attempt to load {$filename} was unsuccessful." );
		}
		$this->_loaded = true;
		$this->_loadedFiles [] = $filename;
		if (! $this->_loadFromCache ()) {
			$this->_load ( $filename );
			$this->_saveToCache ();
		}
	}
	/**
	 * Extends configuration by another one, whose file name (path) is given in 
	 * parameter.
	 *
	 * @param string $filename Configuration file name (path)
	 */
	public function extendBy($filename) {
		if (! $this->_loaded) {
			require_once 'SmartL/Config/Exception.php';
			throw new SmartL_Config_Exception ( "Only loaded config can be extended. While extending config by file {$filename}." );
		}
		$this->_loadedFiles [] = $filename;
		if (! $this->_loadFromCache ()) {
			$this->_load ( $filename );
			$this->_saveToCache ();
		}
	}
	
	private function _loadFromCache() {
		if (isset ( self::$_cache )) {
			if (($data = self::$_cache->load ( $this->_getCacheId () )) !== false) {
				$this->unserialize ( $data );
				if (class_exists ( 'SmartL_Profiler' ) and SmartL_Profiler::getInstance ()->isEnabled ()) {
					SmartL_Profiler::getInstance ()->addCacheInfo ( $this->_loadedFiles [count ( $this->_loadedFiles ) - 1] );
				}
				return true;
			}
		}
		return false;
	}
	
	private function _getCacheId() {
		if ($this->_cacheId ['count'] !== count ( $this->_loadedFiles )) {
			$this->_cacheId ['id'] = md5 ( implode ( ';', $this->_loadedFiles ) );
		}
		return $this->_cacheId ['id'];
	}
	
	private function _saveToCache() {
		if (isset ( self::$_cache )) {
			self::$_cache->save ( $this->serialize (), $this->_getCacheId () );
		}
	}
	
	private function _load($filename) {
		$xmlDoc = new DOMDocument ( );
		$this->_dirname = dirname ( $filename ); //die;
		if (! $xmlDoc->load ( $filename )) {
			require_once 'SmartL/Config/Exception.php';
			throw new SmartL_Config_Exception ( "File {$filename} with configuration can't be loaded." );
		}
		$rootNode = $xmlDoc->getElementsByTagName ( "config" );
		if ($rootNode->length != 1) {
			require_once 'SmartL/Config/Exception.php';
			throw new SmartL_Config_Exception ( "File {$filename} with configuration can't be loaded. 
				Make sure xml file has root node with na config." );
		}
		$this->_createRootNode ( $rootNode->item ( 0 ) );
	}
	/**
	 * Creates data of this config class from xml node.
	 * @param DOMElement $node xml data.
	 */
	private function _createRootNode(DOMElement $node) {
		if ($node->hasAttribute ( "defaultRewriteMode" )) {
			$rewriteMode = $node->getAttribute ( "defaultRewriteMode" )->value;
			if ($this->_isRightRewriteModeString ( $rewriteMode )) {
				$this->_defaultRewriteMode = $rewriteMode;
			} else {
				require_once ("Config/Exception.php");
				throw new SmartL_Config_Exception ( "Wrong format of rewrite mode in cofing. '{$rewriteMode}' given." );
			}
		} else {
			$this->_defaultRewriteMode = SmartL_Config_Storage::REWRITE_MODE_NONE;
		}
		$this->_addChildren ( $node, $this );
	}
	private function _getRewriteMode(DOMElement $node, $default) {
		if ($node->hasAttribute ( "rewriteMode" )) {
			$result = $node->getAttribute ( "rewriteMode" );
			if ($this->_isRightRewriteModeString ( $result )) {
				return $result;
			} else {
				require_once ("Config/Exception.php");
				throw new SmartL_Config_Exception ( "Wrong format of rewrite mode in cofing. '{$result}' given." );
			}
		}
		if ($default == SmartL_Config_Storage::REWRITE_MODE_LIST) {
			if ($node->nodeName == SmartL_Config_Storage_Array::CHILD_UPDATE_TAG or $node->nodeName == SmartL_Config_Storage_Array::CHILD_ADD_TAG) {
				$default = SmartL_Config_Storage::REWRITE_MODE_REWRITE;
			} else {
				$default = SmartL_Config_Storage::REWRITE_MODE_NONE;
			}
		}
		return $default;
	}
	/**
	 * Adds attributes from xml data in node to storage.
	 * @param DOMElement $node xml data with attributes
	 * @param SmartL_Config_Storage $storage reference to data storage
	 */
	private function _addAttributes(DOMElement $node, SmartL_Config_Storage $storage) {
		if ($node->hasAttributes ()) {
			foreach ( $node->attributes as $attribute ) {
				if ($attribute->name == SmartL_Config_Storage::REWRITE_MODE_ATTRIBUTE)
					continue;
				$name = $attribute->name;
				$rewrite = (isset ( $storage->$name ) and $storage->getRewriteMode () == SmartL_Config_Storage::REWRITE_MODE_REWRITE);
				if ($rewrite) {
					$child = $storage->$name;
				} else {
					$child = new SmartL_Config_Storage ( );
				}
				$child->setValue ( trim ( $attribute->value ) );
				$child->setRewriteMode ( $storage->getRewriteMode () );
				if (! $rewrite)
					$storage->addChild ( $name, $child );
			}
		}
	}
	/**
	 * Adds all children nodes, not attributes.
	 * @param DOMElement $node xml data of parent.
	 * @param SmartL_Config_Storage $storage reference to parent storage.
	 */
	private function _addChildren(DOMElement $node, SmartL_Config_Storage $storage) {
		if ($node->hasChildNodes ()) {
			foreach ( $node->childNodes as $child ) {
				if (get_class ( $child ) == "DOMElement") {
					if ($child->nodeName == self::INCLUDE_TAG) {
						//including
						$this->_includeFile ( $child, $storage );
					} else {
						//usual node
						$this->_addChild ( $storage, $child );
					}
				}
			}
		}
	}
	private function _includeFile(DOMElement $includeNode, SmartL_Config_Storage $parent) {
		if (! $includeNode->hasAttribute ( self::INCLUDE_FILE_ATTRIBUTE )) {
			require_once ("Config/Exception.php");
			throw new SmartL_Config_Exception ( "Include tag must contain file attribute." );
		}
		$data = new SmartL_Config ( );
		$filename = $this->_dirname . '/' . $includeNode->getAttribute ( self::INCLUDE_FILE_ATTRIBUTE );
		$data->load ( $filename );
		foreach ( $data as $name => $child ) {
			if (is_array ( $child )) {
				foreach ( $child as $nextChild ) {
					$parent->addChild ( $name, $nextChild );
				}
			} else {
				$parent->addChild ( $name, $child );
			}
		}
	
	}
	/**
	 * Adds storage with data of child to parent storage. It also adds attributes.
	 * @param SmartL_Config_Storage $storage reference to parent storage
	 * @param DOMElement $child data of child in xml 
	 */
	private function _addChild(SmartL_Config_Storage $storage, DOMElement $child) {
		$childNodeName = $child->nodeName;
		if (isset ( $storage->$childNodeName )) {
			$origRewriteMode = $storage->getChildRewriteMode ( $childNodeName );
		}
		$rewriteMode = $this->_getRewriteMode ( $child, $storage->getRewriteMode () );
		if (isset ( $origRewriteMode ) and $rewriteMode != $origRewriteMode) {
			require_once ("Config/Exception.php");
			throw new SmartL_Config_Exception ( "Rewrite mode of tag {$child->nodeName} must be same in extended config. '{$rewriteMode}' given compared with current '{$origRewriteMode}' of node {$childNodeName}." );
		}
		//creates right type of storage or find child storage.
		$children = $storage->getChildren ();
		if ($storage->getRewriteMode () == SmartL_Config_Storage::REWRITE_MODE_LIST) {
			$children = $children [SmartL_Config_Storage_Array::CHILD_ADD_TAG];
			$childNodeName = SmartL_Config_Storage::getNodeIndex ( $child );
		}
		$rewriting = (isset ( $children [$childNodeName] ) and ($rewriteMode == SmartL_Config_Storage::REWRITE_MODE_LIST or $rewriteMode == SmartL_Config_Storage::REWRITE_MODE_REWRITE or $rewriteMode == SmartL_Config_Storage::REWRITE_MODE_DEEPREWRITE));
		if ($rewriting) {
			if (is_array ( $children [$childNodeName] )) {
				//They are indexed in array
				if (isset ( $children [$childNodeName] [SmartL_Config_Storage::getNodeIndex ( $child )] )) {
					$childStorage = $children [$childNodeName] [SmartL_Config_Storage::getNodeIndex ( $child )];
				} else {
					$rewriting = false;
					$childStorage = new SmartL_Config_Storage ( );
				}
			} else {
				//Child node with given name already exists and some type of rewriting is set.				
				$childStorage = $children [$childNodeName];
				if ($rewriteMode == SmartL_Config_Storage::REWRITE_MODE_DEEPREWRITE) {
					$childStorage->clearChildren ();
				}
			}
		} else //else - Child node with given name doesn't exist or exist but with rewrite mode se to 'none'.
if ($rewriteMode == SmartL_Config_Storage::REWRITE_MODE_LIST) {
			$childStorage = new SmartL_Config_Storage_Array ( );
		} else {
			$childStorage = new SmartL_Config_Storage ( );
		}
		//set values
		$childStorage->setRewriteMode ( $rewriteMode );
		$childStorage->setValue ( trim ( $child->nodeValue ) );
		$this->_addAttributes ( $child, $childStorage );
		$this->_addChildren ( $child, $childStorage );
		//add child to its parent
		if (! $rewriting)
			$storage->addChild ( $child->nodeName, $childStorage );
	}
	protected function _isRightRewriteModeString($string) {
		return ($string == SmartL_Config_Storage::REWRITE_MODE_LIST or $string == SmartL_Config_Storage::REWRITE_MODE_NONE or $string == SmartL_Config_Storage::REWRITE_MODE_REWRITE or $string == SmartL_Config_Storage::REWRITE_MODE_DEEPREWRITE);
	}
	
	public function serialize() {
		$result = $this->_defaultRewriteMode . ';' . parent::serialize ();
		return $result;
	}
	
	public function unserialize($serialized) {
		$this->_loaded = true;
		$this->_defaultRewriteMode = $this->_next ( $serialized );
		parent::unserialize ( $serialized );
	}
}